package com.anji.hyperneat.onlinereinforcement;

import com.anji.hyperneat.nd.NDFloatArray;
import com.anji.hyperneat.nd.NDFloatArray.MatrixIterator;
import com.anji.hyperneat.onlinereinforcement.ActivatorNDLR;
import com.anji.nn.activationfunction.ActivationFunction;
import com.anji.nn.activationfunction.ActivationFunctionFactory;


public class RandomGridNet extends GridNetNDLR implements ActivatorNDLR, Cloneable {

	public RandomGridNet(NDFloatArray[] layers
			, NDFloatArray[] weights
			, NDFloatArray[] bias
			, NDFloatArray[] weightLearningRates
			, NDFloatArray[] biasLearningRates
			, NDFloatArray[] weightLearningRateDecays
			, NDFloatArray[] biasLearningRateDecays
			, ActivationFunction activationFunction
			, int maxDimensions
			, int cyclesPerStep
			, boolean enableBias
			, String name
			, LearningRateGranularity lrg
			, LearningRateGranularity lrdg
			, boolean useDecay
		) {
		super(layers, weights, bias, weightLearningRates, biasLearningRates, weightLearningRateDecays, biasLearningRateDecays, activationFunction, maxDimensions, cyclesPerStep, enableBias, name, lrg, lrdg, useDecay);
		
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// 2BIT OUTPUT AND
//		RandomGridNet net = getRandomGridNet(2, new int[][] {{2}, {2}});
//		ActivatorNDBackPropagator bp = new ActivatorNDBackPropagator(net);
//		
//		NDFloatArray[] in = new NDFloatArray[4];
//		in[0] = new NDFloatArray(new int[] {2}); in[0].set(0.0f, 0); in[0].set(0.0f, 1);
//		in[1] = new NDFloatArray(new int[] {2}); in[1].set(0.0f, 0); in[1].set(1.0f, 1);
//		in[2] = new NDFloatArray(new int[] {2}); in[2].set(1.0f, 0); in[2].set(0.0f, 1);
//		in[3] = new NDFloatArray(new int[] {2}); in[3].set(1.0f, 0); in[3].set(1.0f, 1);
//		
//		
//		NDFloatArray[] expected = new NDFloatArray[4];
//		expected[0] = new NDFloatArray(new int[] {2}); expected[0].set(0.0f, 0);  expected[0].set(1.0f, 1);
//		expected[1] = new NDFloatArray(new int[] {2}); expected[1].set(0.0f, 0);  expected[1].set(1.0f, 1);
//		expected[2] = new NDFloatArray(new int[] {2}); expected[2].set(0.0f, 0);  expected[2].set(1.0f, 1);
//		expected[3] = new NDFloatArray(new int[] {2}); expected[3].set(1.0f, 0);  expected[3].set(0.0f, 1);

		// ORIG AND
//		RandomGridNet net = getRandomGridNet(2, new int[][] {{2}, {1}});
//		ActivatorNDBackPropagator bp = new ActivatorNDBackPropagator(net);
//		
//		NDFloatArray[] in = new NDFloatArray[4];
//		in[0] = new NDFloatArray(new int[] {2}); in[0].set(0.0f, 0); in[0].set(0.0f, 1);
//		in[1] = new NDFloatArray(new int[] {2}); in[1].set(0.0f, 0); in[1].set(1.0f, 1);
//		in[2] = new NDFloatArray(new int[] {2}); in[2].set(1.0f, 0); in[2].set(0.0f, 1);
//		in[3] = new NDFloatArray(new int[] {2}); in[3].set(1.0f, 0); in[3].set(1.0f, 1);
//		
//		
//		NDFloatArray[] expected = new NDFloatArray[4];
//		expected[0] = new NDFloatArray(new int[] {1}); expected[0].set(0.0f, 0);
//		expected[1] = new NDFloatArray(new int[] {1}); expected[1].set(0.0f, 0);
//		expected[2] = new NDFloatArray(new int[] {1}); expected[2].set(0.0f, 0);
//		expected[3] = new NDFloatArray(new int[] {1}); expected[3].set(1.0f, 0);
		
		// ORIG XOR
//		RandomGridNet net = getRandomGridNet(3, new int[][] {{2}, {3}, {1}});
//		ActivatorNDBackPropagator bp = new ActivatorNDBackPropagator(net);
//		
//		NDFloatArray[] in = new NDFloatArray[4];
//		in[0] = new NDFloatArray(new int[] {2}); in[0].set(0.0f, 0); in[0].set(0.0f, 1);
//		in[1] = new NDFloatArray(new int[] {2}); in[1].set(0.0f, 0); in[1].set(1.0f, 1);
//		in[2] = new NDFloatArray(new int[] {2}); in[2].set(1.0f, 0); in[2].set(0.0f, 1);
//		in[3] = new NDFloatArray(new int[] {2}); in[3].set(1.0f, 0); in[3].set(1.0f, 1);
//		
//		
//		NDFloatArray[] expected = new NDFloatArray[4];
//		expected[0] = new NDFloatArray(new int[] {1}); expected[0].set(0.0f, 0);
//		expected[1] = new NDFloatArray(new int[] {1}); expected[1].set(1.0f, 0);
//		expected[2] = new NDFloatArray(new int[] {1}); expected[2].set(1.0f, 0);
//		expected[3] = new NDFloatArray(new int[] {1}); expected[3].set(0.0f, 0);	
		
		// 2BIT OUTPUT XOR
		RandomGridNet net = getRandomGridNet(3, new int[][] {{2}, {3}, {2}});
		ActivatorNDBackPropagator bp = new ActivatorNDBackPropagator(net);
		
		NDFloatArray[] in = new NDFloatArray[4];
		in[0] = new NDFloatArray(new int[] {2}); in[0].set(0.0f, 0); in[0].set(0.0f, 1);
		in[1] = new NDFloatArray(new int[] {2}); in[1].set(0.0f, 0); in[1].set(1.0f, 1);
		in[2] = new NDFloatArray(new int[] {2}); in[2].set(1.0f, 0); in[2].set(0.0f, 1);
		in[3] = new NDFloatArray(new int[] {2}); in[3].set(1.0f, 0); in[3].set(1.0f, 1);
		
		
		NDFloatArray[] expected = new NDFloatArray[4];
		expected[0] = new NDFloatArray(new int[] {2}); expected[0].set(0.0f, 0);  expected[0].set(1.0f, 1);
		expected[1] = new NDFloatArray(new int[] {2}); expected[1].set(1.0f, 0);  expected[1].set(0.0f, 1);
		expected[2] = new NDFloatArray(new int[] {2}); expected[2].set(1.0f, 0);  expected[2].set(0.0f, 1);
		expected[3] = new NDFloatArray(new int[] {2}); expected[3].set(0.0f, 0);  expected[3].set(1.0f, 1);
		
		
		
		for (int i = 0; i < 3000; i++) {
			for (int input = 0; input < 4; input++) {
				print(in[input]);
				
				print(": ");
				
				NDFloatArray outs = bp.next(in[input]);
				print(outs);
				
				print("\tWgtsPre: ");
				print(bp.getWeights());
				print("\tBiasPre: ");
				print(bp.getBias());
				
				bp.updateNet(expected[input]);
				print("\tWgtsPost: ");
				print(bp.getWeights());
				print("\tBiasPost: ");
				print(bp.getBias());
				println("");
			}
			println("...");
		}
	}
	
	private static void print(NDFloatArray[] m) {
		for (int i = 0; i < m.length; i++) {
			print("[");
			for (MatrixIterator it = m[i].iterator(); it.hasNext(); it.next())
				print(it.get() + " ");
			print("] ");
		}
	}
	
	private static void print(Object m) {
		System.out.print(m);
	}
	
	private static void println(Object m) {
		System.out.println(m);
	}
	
	@Override
	public float getWeightLearningRate(int layer, int... coords) {
		return super.getWeightLearningRate(layer, coords);
	}

	@Override
	public float getBiasLearningRate(int layer, int... coords) {
		return super.getBiasLearningRate(layer, coords);
	}

	@Override
	public LearningRateGranularity getLearningRateGranularity() {
		return super.getLearningRateGranularity();
	}
	
	@Override
	public RandomGridNet clone() {
		
	    ActivationFunction activationFunction = this.getActivationFunction();
	    NDFloatArray[] layers = new NDFloatArray[this.getLayers().length];
	    for (int i = 0; i < layers.length; i++) layers[i] = this.getLayers()[i].clone();
		NDFloatArray[] weights = new NDFloatArray[this.getWeights().length];
		for (int i = 0; i < weights.length; i++) weights[i] = this.getWeights()[i].clone();
		NDFloatArray[] bias = new NDFloatArray[this.getBias().length];
		for (int i = 0; i < bias.length; i++) bias[i] = this.getBias()[i].clone();
		NDFloatArray[] wgtLearningRates = new NDFloatArray[this.getWeightLearningRates().length];
		for (int i = 0; i < wgtLearningRates.length; i++) wgtLearningRates[i] = this.getWeightLearningRates()[i].clone();
		NDFloatArray[] biasLearningRates = new NDFloatArray[this.getBiasLearningRates().length];
		for (int i = 0; i < biasLearningRates.length; i++) biasLearningRates[i] = this.getBiasLearningRates()[i].clone();
		
		
		int maxDimensions = this.getMaxDimensions();
	    String name = new String(this.getName());
	    int cyclesPerStep = this.getCyclesPerStep();
	    
	    RandomGridNet clone = new RandomGridNet(layers, weights, bias, wgtLearningRates, biasLearningRates, null, null, activationFunction, maxDimensions, cyclesPerStep, getEnableBias(), name, this.getLearningRateGranularity(), this.getLearningRateDecayGranularity(), this.isUseDecay());
	    
		return clone;
		
	}

	@Override
	public void decayLearningRates() {
		super.decayLearningRates();		
	}

	public static RandomGridNet getRandomGridNet(int numLayers, int[][] layerDimensions) {
		NDFloatArray[] layers = new NDFloatArray[numLayers];
		for (int i = 0; i < numLayers; i++) layers[i] = new NDFloatArray(layerDimensions[i]);
		
		int maxDimensions = getMaxDim(layerDimensions);
		
		NDFloatArray[] weights = createWeights(numLayers, layerDimensions, maxDimensions);
		NDFloatArray[] bias = createBias(numLayers, layerDimensions, maxDimensions);
		
		ActivationFunction activationFunction = ActivationFunctionFactory.getInstance().get("sigmoid");
		
		NDFloatArray[] weightLearningRates = new NDFloatArray[1];
		weightLearningRates[0] = new NDFloatArray(new int[] {1});
		weightLearningRates[0].set(0.2f, 0);
		
		NDFloatArray[] biasLearningRates = new NDFloatArray[1];
		biasLearningRates[0] = new NDFloatArray(new int[] {1});
		biasLearningRates[0].set(0.2f, 0);

		
		RandomGridNet net = new RandomGridNet( layers
				, weights
				, bias
				, weightLearningRates
				, biasLearningRates
				, null
				, null
				, activationFunction
				, maxDimensions
				, 0
				, true
				, "test"
				, LearningRateGranularity.SINGLE
				, LearningRateGranularity.SINGLE
				, false
			);
		return net;
	}
	
	private static NDFloatArray[] createBias(int numLayers, int[][] layerDimensions, int maxDimensions) {
		NDFloatArray[] bias = new NDFloatArray[numLayers-1];
		for (int i = 1; i < numLayers; i++) {
			bias[i-1] = new NDFloatArray(maxDimensions, layerDimensions[i]);
			for (MatrixIterator it = bias[i-1].iterator(); it.hasNext(); it.next())
				it.set((float) (Math.random() * 2.0 - 1.0));
		}
		
		return bias;
	}

	private static NDFloatArray[] createWeights(int numLayers, int[][] layerDimensions, int maxDimensions) {
		NDFloatArray[] weights = new NDFloatArray[numLayers-1];
		for (int i = 0; i < numLayers - 1; i++) {
			weights[i] = new NDFloatArray(maxDimensions, layerDimensions[i+1], layerDimensions[i]);
			for (MatrixIterator it = weights[i].iterator(); it.hasNext(); it.next())
				it.set((float) (Math.random() * 2.0 - 1.0));
		}
		
		return weights;
	}

	private static int getMaxDim(int[][] a) {
		int max = -1;
		for (int i = 0; i < a.length; i++)
			if (a[i].length > max) max = a[i].length;
		return max;
	}
}
